use crate::hardware::{Interposer, COB_ROW_SIZE, COB_COL_SIZE};
use crate::hardware::{COBDirection, COBCoord};
use crate::hardware::TOBCoord;

use super::{COB_SEL_REGEX, COB_SW_REGEX, TOB_HCTRL_REGEX, TOB_VCTRL_REGEX, TOB_BANK_SEL_REGEX};

use std::error::Error;
use std::path::Path;

pub fn read_controlbits<P: AsRef<Path>>(paths: &[P]) -> Result<Interposer, Box<dyn Error>> {
    let interposer = Interposer::new();

    for path in paths {
        println!("Read file '{}'", path.as_ref().display());
        let content = std::fs::read_to_string(path.as_ref())?;
        for line in content.lines() {
            if let Some(captures) = COB_SW_REGEX.captures(line) {
                let value = u32::from_str_radix(&captures[1], 16).unwrap();
                let row = captures[3].parse::<i64>().unwrap();
                let col = captures[4].parse::<i64>().unwrap();
                let dir = &captures[5];
                let group = captures[6].parse::<usize>().unwrap();

                assert!(col < COB_COL_SIZE);
                assert!(row < COB_ROW_SIZE);

                set_cob_sw_register_value(&interposer, value, row, col, dir, group);
            } else if let Some(captures) = COB_SEL_REGEX.captures(line) {
                let value = u32::from_str_radix(&captures[1], 16).unwrap();
                let row = captures[3].parse::<i64>().unwrap();
                let col = captures[4].parse::<i64>().unwrap();
                let dir = &captures[5];
                let group = captures[6].parse::<usize>().unwrap();

                assert!(col < COB_COL_SIZE);
                assert!(row < COB_ROW_SIZE);

                set_cob_sel_register_value(&interposer, value, row, col, dir, group)
            } else if let Some(captures) = TOB_HCTRL_REGEX.captures(line) {
                let value = u32::from_str_radix(&captures[1], 16).unwrap();
                let row = captures[3].parse::<i64>().unwrap();
                let col = captures[4].parse::<i64>().unwrap();
                let bank_index = captures[5].parse::<usize>().unwrap();
                let group = captures[6].parse::<usize>().unwrap();

                assert!(col < COB_COL_SIZE);
                assert!(row < COB_ROW_SIZE);

                let tob = interposer.get_tob(&TOBCoord::new(row, col)).unwrap();
                for i in 0..8 {
                    let output_idx = ((value >> (i*3)) & 0b111) as usize;
                    tob.b_to_h_register_nth(bank_index * 8 + group, i).set(output_idx);
                }

            } else if let Some(captures) = TOB_VCTRL_REGEX.captures(line) {
                let value = u32::from_str_radix(&captures[1], 16).unwrap();
                let row = captures[3].parse::<i64>().unwrap();
                let col = captures[4].parse::<i64>().unwrap();
                let bank_index = captures[5].parse::<usize>().unwrap();
                let group = captures[6].parse::<usize>().unwrap();

                assert!(col < COB_COL_SIZE);
                assert!(row < COB_ROW_SIZE);

                let tob = interposer.get_tob(&TOBCoord::new(row, col)).unwrap();
                for i in 0..8 {
                    let output_idx = ((value >> (i*3)) & 0b111) as usize;
                    tob.h_to_v_register_nth(bank_index * 8 + group, i).set(output_idx);
                }

            } else if let Some(captures) = TOB_BANK_SEL_REGEX.captures(line) {
                let value = u32::from_str_radix(&captures[1], 16).unwrap();
                let row = captures[3].parse::<i64>().unwrap();
                let col = captures[4].parse::<i64>().unwrap();
                let bank_index = captures[5].parse::<usize>().unwrap();

                assert!(col < COB_COL_SIZE);
                assert!(row < COB_ROW_SIZE);

                let tob = interposer.get_tob(&TOBCoord::new(row, col)).unwrap();
                for i in 0..32 {
                    let output_idx = ((value >> i) & 0b1) as usize;
                    let oop_idx = if output_idx == 1 { 0 } else { 1 };
                    tob.v_to_t_register_nth(bank_index * 32 + i, 0).set(output_idx);
                    tob.v_to_t_register_nth(bank_index * 32 + i, 1).set(oop_idx);
                }
            }
        }
    }
    println!("");

    Ok(interposer)
}

fn set_cob_sw_register_value(interposer: &Interposer, value: u32, row: i64, col: i64, dir: &str, group: usize) {
    let cobcoord = COBCoord::new(row, col);
    let cob = interposer.get_cob(&cobcoord).unwrap();
    use COBDirection::*;
    match dir {
        "ru" => {
            for i in 0..32 {
                let val = ((value >> i) & 0x1) == 1;
                cob.set_sw_register_value(Right, group * 32 + i, Up, val);
            }
        }
        "lu" => {
            for i in 0..32 {
                let val = ((value >> i) & 0x1) == 1;
                cob.set_sw_register_value(Left, group * 32 + i, Up, val);
            }
        }
        "rd" => {
            for i in 0..32 {
                let val = ((value >> i) & 0x1) == 1;
                cob.set_sw_register_value(Right, group * 32 + i, Down, val);
            }
        }
        "ld" => {
            for i in 0..32 {
                let val = ((value >> i) & 0x1) == 1;
                cob.set_sw_register_value(Left, group * 32 + i, Down, val);
            }
        }
        "h" => {
            for i in 0..32 {
                let val = ((value >> i) & 0x1) == 1;
                cob.set_sw_register_value(Left, group * 32 + i, Right, val);
            }
        }
        "v" => {
            for i in 0..32 {
                let val = ((value >> i) & 0x1) == 1;
                cob.set_sw_register_value(Up, group * 32 + i, Down, val);
            }
        }
        _ => unreachable!("Unkonw dir: '{}'", dir),
    }
}

fn set_cob_sel_register_value(interposer: &Interposer, value: u32, row: i64, col: i64, dir: &str, group: usize) {
    let cobcoord = COBCoord::new(row, col);
    let cob = interposer.get_cob(&cobcoord).unwrap();

    let dir = match dir {
        "left" => COBDirection::Left,
        "right" => COBDirection::Right,
        "up" => COBDirection::Up,
        "down" => COBDirection::Down,
        _ => unreachable!(),
    };

    for i in 0..32 {
        let val = (((value >> i) & 0x1) == 1).into();
        let index = group * 32 + i;
        cob.set_sel_register_value(dir, index, val);
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn test_format_register_value() {
        assert_eq!(format!("{:08x}", 0 as u32), "00000000");

        assert_eq!(format!("{:08x}", 0x00020000 as u32), "00020000");
    }

    #[test]
    fn test_shift() {
        let b = ((2 >> 1) & 0x1) == 1;
        assert!(b);
    }
}
